#region Copyright

/*SourceGrid LICENSE (MIT style)

Copyright (c) 2005 - 2012 http://sourcegrid.codeplex.com/, Davide Icardi, Darius Damalakas

Permission is hereby granted, free of charge, to any person obtaining 
a copy of this software and associated documentation files (the "Software"), 
to deal in the Software without restriction, including without limitation 
the rights to use, copy, modify, merge, publish, distribute, sublicense, 
and/or sell copies of the Software, and to permit persons to whom the 
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included 
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE. */

//------------------------------------------------------------------------ 
// Copyright (C) Siemens AG 2016    
//------------------------------------------------------------------------ 
// Project           : UIGrid
// Author            : Sandhra.Prakash@siemens.com
// In Charge for Code: Sandhra.Prakash@siemens.com
//------------------------------------------------------------------------ 

/*Changes :
 * 1. DataGrid::GetCell(int p_iRow, int p_iCol): changed code to support filter cell.
 * 2. Datagrid ::CreateColumns made virtual.
 * 3. DataGridValueModel :: SetValue - check added to check if newvalue and oldvalue is the same. 
                                        if same no need to write back to datasource or trigger valuechanged event.
 * 4. DataGridValueModel :: SetValue - valuechanging will be used to get validation from user. 
                                        If user sets cancel true, then changes will be rejected
 * 5. DataGridCellController:: OnEditStarting - if e.Cancel is already set to true in some other
                                                controller no need to proceed
 * 6. DataGrid::BeginEditRow, CreateColumns, GetCell : changed fixedRows and fixedColumns to headerRowCount\HeaderColumnCount for FreezePanes enhancement.
*/
#endregion Copyright

using SourceGrid.Selection;
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using DevAge.ComponentModel;

namespace SourceGrid
{
	/// <summary>
	/// A grid control that support load from a System.Data.DataView class, usually used for data binding.
	/// </summary>
	[System.ComponentModel.ToolboxItem(true)]
	public class DataGrid : GridVirtual
	{
		public DataGrid()
		{
			FixedRows = 1;
			FixedColumns = 0;

			Controller.AddController(new DataGridCellController());

			SelectionMode = GridSelectionMode.Row;
		}

		protected override void Dispose(bool disposing)
		{
			base.Dispose (disposing);
		}

		/// <summary>
		/// Method used to create the rows object, in this class of type DataGridRows.
		/// </summary>
		protected override RowsBase CreateRowsObject()
		{
			return new DataGridRows(this);
		}

		/// <summary>
		/// Method used to create the columns object, in this class of type DataGridColumns.
		/// </summary>
		protected override ColumnsBase CreateColumnsObject()
		{
			return new DataGridColumns(this);
		}

		protected override SelectionBase CreateSelectionObject()
		{
			SourceGrid.Selection.SelectionBase selObj = base.CreateSelectionObject();

			selObj.EnableMultiSelection = false;
			selObj.FocusStyle = SourceGrid.FocusStyle.RemoveFocusCellOnLeave;
			selObj.FocusRowLeaving += new RowCancelEventHandler(Selection_FocusRowLeaving);

			return selObj;
		}

		private DevAge.ComponentModel.IBoundList mBoundList;

		public override bool EnableSort{
			get{
				if (DataSource == null)
					return false;
				return DataSource.AllowSort;
			}
			set
			{
				if (DataSource == null)
					return;
				DataSource.AllowSort = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the IBoundList used for data binding.
		/// It can be any class that implements the IBoundList interface, usually can be BoundList
		///  (that can be used to bind to a generic List) or BoundDataView (that can be used to bind to a DataView).
		/// </summary>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public DevAge.ComponentModel.IBoundList DataSource
		{
			get { return mBoundList; }
			set
			{
				Unbind();
				mBoundList = value;
				if (mBoundList != null)
					Bind();
			}
		}

		protected virtual void Unbind()
		{
			if (mBoundList != null)
			{
				mBoundList.ListChanged -= new ListChangedEventHandler(mBoundList_ListChanged);
				mBoundList.ItemDeleted -= new ItemDeletedEventHandler(mBoundList_ItemDeleted);
				mBoundList.ListCleared -= new EventHandler(mBoundList_ListCleared);
			}

			Rows.RowsChanged();
		}

		protected virtual void Bind()
		{
			if (Columns.Count == 0)
				CreateColumns();
			InvalidateDataGridColumns();

			mBoundList.ListChanged += new ListChangedEventHandler(mBoundList_ListChanged);
			mBoundList.ItemDeleted += new ItemDeletedEventHandler(mBoundList_ItemDeleted);
			mBoundList.ListCleared += new EventHandler(mBoundList_ListCleared);
            
			Rows.RowsChanged();
			Rows.ResetRowHeigth();
		}

		void mBoundList_ListCleared ( object sender, EventArgs e )
		{
			Rows.ResetRowHeigth();
		}

		void mBoundList_ItemDeleted ( object sender, DevAge.ComponentModel.ItemDeletedEventArgs e )
		{
			Rows.RowDeleted(e.Item);
		}

		private void InvalidateDataGridColumns()
		{
			foreach (DataGridColumn column in Columns)
			{
				column.Invalidate();
			}
		}
		
		/// <summary>
		/// Gets the rows information as a DataGridRows object.
		/// </summary>
		public new DataGridRows Rows
		{
			get{return (DataGridRows)base.Rows;}
		}

		/// <summary>
		/// Gets the columns informations as a DataGridColumns object.
		/// </summary>
		public new DataGridColumns Columns
		{
			get{return (DataGridColumns)base.Columns;}
		}

		protected virtual void mBoundList_ListChanged(object sender, ListChangedEventArgs e)
		{
			if (base.IsSuspended() == true)
				return;
			Rows.RowsChanged();
			Invalidate(true);
		}

		/// <summary>
		/// Gets a specified Cell by its row and column.
		/// </summary>
		/// <param name="p_iRow"></param>
		/// <param name="p_iCol"></param>
		/// <returns></returns>
		public override Cells.ICellVirtual GetCell(int p_iRow, int p_iCol)
		{
			if (mBoundList == null)
				return null;
			if (p_iCol >= Columns.Count)
				return null;

            //sandhra.prakash@siemens.com: changed fixedRows to headerRowCount for FreezePanes enhancement.
            if (p_iRow < HeaderRowCount)
            {
                if (p_iRow <= 0)
                {
                    return Columns[p_iCol].HeaderCell;
                }
                else
                {
                    //Sandhra.prakash@siemens.com: To Support Filter cell.
                    return Columns[p_iCol].FilterCell;
                }
            }
            else
            {
                return Columns[p_iCol].GetDataCell(p_iRow);
            }
		}

		protected override void OnSortingRangeRows(SortRangeRowsEventArgs e)
		{
			base.OnSortingRangeRows (e);

			if (DataSource == null || DataSource.AllowSort == false)
				return;

			System.ComponentModel.PropertyDescriptor propertyCol = Columns[e.KeyColumn].PropertyColumn;

			if (propertyCol != null)
			{
				ListSortDirection direction;
				if (e.Ascending)
					direction = ListSortDirection.Ascending;
				else
					direction = ListSortDirection.Descending;
				ListSortDescription[] sortsArray = new ListSortDescription[1];
				sortsArray[0] = new ListSortDescription(propertyCol, direction);

				DataSource.ApplySort(new ListSortDescriptionCollection(sortsArray));
			}
			else
				DataSource.ApplySort(null);
		}

		/// <summary>
		/// Automatic create the columns classes based on the specified DataSource.
		/// </summary>
		public virtual void CreateColumns()
		{
			Columns.Clear();
			if (DataSource != null)
			{
				int i = 0;


                //sandhra.prakash@siemens.com: changed fixedColumns to headerColumnCount for FreezePanes enhancement.
				if (HeaderColumnCount > 0)
				{
					Columns.Insert(i, DataGridColumn.CreateRowHeader(this));
					i++;
				}

				foreach (System.ComponentModel.PropertyDescriptor prop in DataSource.GetItemProperties())
				{
					Columns.Add(prop.Name,
					            prop.DisplayName,
					            SourceGrid.Cells.DataGrid.Cell.Create(prop.PropertyType, !prop.IsReadOnly));
				}
			}
		}

		/// <summary>
		/// Gets or sets the selected DataRowView.
		/// </summary>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public object[] SelectedDataRows
		{
			get
			{
				if (mBoundList == null)
					return new System.Data.DataRowView[0];

				int[] rowsSel = Selection.GetSelectionRegion().GetRowsIndex();

				int count = 0;
				for (int i = 0; i < rowsSel.Length; i++)
				{
					object objRow= Rows.IndexToDataSourceRow(rowsSel[i]);
					if (objRow != null)
						count++;
				}

				object[] dataRows = new object[count];
				int indexRows = 0;
				for (int i = 0; i < rowsSel.Length; i++)
				{
					object objRow = Rows.IndexToDataSourceRow(rowsSel[i]);
					if (objRow != null)
					{
						dataRows[indexRows] = objRow;
						indexRows++;
					}
				}
				return dataRows;
			}
			set
			{
				Selection.ResetSelection(false);

				if (mBoundList != null && value != null)
				{
					for (int i = 0; i < value.Length; i++)
					{
						for (int r = FixedRows; r < Rows.Count; r++)
						{
							object objRow = Rows.IndexToDataSourceRow(r);

							if (object.ReferenceEquals(objRow, value[i]))
							{
								Selection.SelectRow(r, true);
								break;
							}
						}
					}
				}
			}
		}

		protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
		{
			base.OnKeyDown(e);

			if (e.KeyCode == System.Windows.Forms.Keys.Delete &&
			    mBoundList != null &&
			    mBoundList.AllowDelete &&
			    e.Handled == false &&
			    mDeleteRowsWithDeleteKey)
			{
				object[] rows = SelectedDataRows;
				if (rows != null && rows.Length > 0)
					DeleteSelectedRows();

				e.Handled = true;
			}
			else if (e.KeyCode == System.Windows.Forms.Keys.Escape &&
			         e.Handled == false &&
			         mCancelEditingWithEscapeKey)
			{
				EndEditingRow(true);

				e.Handled = true;
			}
		}


		protected override void OnValidating(CancelEventArgs e)
		{
			base.OnValidating(e);

			try
			{
				if (EndEditingRowOnValidate)
				{
					EndEditingRow(false);
				}
			}
			catch (Exception ex)
			{
				OnUserException(new ExceptionEventArgs( ex ));
			}
		}

		private bool mEndEditingRowOnValidate = true;
		/// <summary>
		/// Gets or sets a property to force an End Editing when the control loose the focus
		/// </summary>
		[System.ComponentModel.DefaultValue(true)]
		public bool EndEditingRowOnValidate
		{
			get { return mEndEditingRowOnValidate; }
			set { mEndEditingRowOnValidate = value; }
		}

		private bool mDeleteRowsWithDeleteKey = true;
		/// <summary>
		/// Gets or sets if enable the delete of the selected rows when pressing Delete key.
		/// </summary>
		[System.ComponentModel.DefaultValue(true)]
		public bool DeleteRowsWithDeleteKey
		{
			get{return mDeleteRowsWithDeleteKey;}
			set{mDeleteRowsWithDeleteKey = value;}
		}

		private bool mCancelEditingWithEscapeKey = true;

		/// <summary>
		/// Gets or sets if enable the Cancel Editing feature when pressing escape key
		/// </summary>
		[System.ComponentModel.DefaultValue(true)]
		public bool CancelEditingWithEscapeKey
		{
			get{return mCancelEditingWithEscapeKey;}
			set{mCancelEditingWithEscapeKey = value;}
		}

		private string mDeleteQuestionMessage = "Are you sure to delete all the selected rows?";
		/// <summary>
		/// Message showed with the DeleteSelectedRows method. Set to null to not show any message.
		/// </summary>
		public string DeleteQuestionMessage
		{
			get{return mDeleteQuestionMessage;}
			set{mDeleteQuestionMessage = value;}
		}

		/// <summary>
		/// Delete all the selected rows.
		/// </summary>
		/// <returns>Returns true if one or more row is deleted otherwise false.</returns>
		public virtual bool DeleteSelectedRows()
		{
			if (string.IsNullOrEmpty(mDeleteQuestionMessage) ||
			    System.Windows.Forms.MessageBox.Show(this, mDeleteQuestionMessage, System.Windows.Forms.Application.ProductName, System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
			{
				foreach (int gridRow in Selection.GetSelectionRegion().GetRowsIndex())
				{
					int dataIndex = Rows.IndexToDataSourceIndex(gridRow);
					if (dataIndex < DataSource.Count)
						DataSource.RemoveAt(dataIndex);
				}

				return true;
			}

			return false;
		}

		/// <summary>
		/// AutoSize the columns based on the visible range and autosize the rows based on it's contents.
		/// </summary>
		public override void AutoSizeCells()
		{
			Columns.AutoSizeView();
			for ( int i = 0; i < Rows.Count; i++ )
				Rows.AutoSizeRow(i);
		}

		private void Selection_FocusRowLeaving(object sender, RowCancelEventArgs e)
		{
			try
			{
				EndEditingRow(false);
			}
			catch(Exception exc)
			{
				OnUserException(new ExceptionEventArgs(new EndEditingException( exc ) ) );

				e.Cancel = true;
			}
		}

		private int? mEditingRow;
		/// <summary>
		/// Check if the specified row is the active row (focused), return false if it is not the active row. Then call the BeginEdit on the associated DataRowView. Add a row to the DataView if required. Returns true if the method sucesfully call the BeginEdit and set the EditingRow property.
		/// </summary>
		/// <param name="gridRow"></param>
		/// <returns></returns>
		public bool BeginEditRow(int gridRow)
		{
            //sandhra.prakash@siemens.com: changed fixedRows to headerRowCount for FreezePanes enhancement.
            if (HeaderRowCount == 2 && gridRow == 1) return true;
			if (mEditingRow != null && mEditingRow.Value == gridRow)
				return true;

			EndEditingRow(false); //Terminate the old edit if present

			if (DataSource != null)
			{
				int dataIndex = Rows.IndexToDataSourceIndex(gridRow);

				// add this here to check if we have permission for edition
				if (!DataSource.AllowEdit)
					return false;

				if (dataIndex == DataSource.Count && DataSource.AllowNew) //Last Row
				{
					DataSource.BeginAddNew();
				}
				else if (dataIndex < DataSource.Count)
				{
					DataSource.BeginEdit(dataIndex);
				}
			}

			mEditingRow = gridRow;

			return true;
		}

		/// <summary>
		/// Calls the CancelEdit or the EndEdit on the editing Row and set to null the editing row.
		/// </summary>
		/// <param name="cancel"></param>
		public void EndEditingRow(bool cancel)
		{
			if (mBoundList != null)
				mBoundList.EndEdit(cancel);

			mEditingRow = null;
		}
	}

	#region Models
	/// <summary>
	/// A Model of type IValueModel used for binding the value to a specified property of the bound object.
	/// Used for the DataGrid control.
	/// </summary>
	public class DataGridValueModel : Cells.Models.IValueModel
	{
		/// <summary>
		/// Constructor
		/// </summary>
		public DataGridValueModel()
		{
		}
		#region IValueModel Members

		public object GetValue(CellContext cellContext)
		{
			DataGrid grid = (DataGrid)cellContext.Grid;

			PropertyDescriptor prop = grid.Columns[cellContext.Position.Column].PropertyColumn;

			int dataIndex = grid.Rows.IndexToDataSourceIndex(cellContext.Position.Row);

			//Check if the row is not outside the valid range (for example to handle the new row)
			if (dataIndex >= grid.DataSource.Count)
				return null;
			else
				return grid.DataSource.GetItemValue(dataIndex, prop);
		}

		public void SetValue(CellContext cellContext, object value)
		{
			DataGrid grid = (DataGrid)cellContext.Grid;
			PropertyDescriptor prop = grid.Columns[cellContext.Position.Column].PropertyColumn;
			object oldValue = GetValue(cellContext);

            //sandhra.prakash@siemens.com: if value is the same, 
            //no need to trigger value changed event or to write back to datasource.
            string oldText = oldValue == null ? string.Empty : oldValue.ToString();
            string newText = value == null ? string.Empty : value.ToString();
            if (newText == oldText)
                return;

			ValueChangeEventArgs valArgs = new ValueChangeEventArgs(oldValue, value);
			if (cellContext.Grid != null)
				cellContext.Grid.Controller.OnValueChanging(cellContext, valArgs);

            //sandhra.prakash@siemens.com: ValueChanging event will be used for validation
            //If operation cancelled then return without setting database. i.e., reject changes
            if (valArgs.Cancel)
                return;

			grid.DataSource.SetEditValue(prop, valArgs.NewValue);

			if (cellContext.Grid != null)
				cellContext.Grid.Controller.OnValueChanged(cellContext, EventArgs.Empty);
		}
		#endregion
	}
	public class DataGridRowHeaderModel : Cells.Models.IValueModel
	{
		public DataGridRowHeaderModel()
		{
		}
		#region IValueModel Members
		public object GetValue(CellContext cellContext)
		{
			DataGrid dataGrid = (DataGrid)cellContext.Grid;
			if (dataGrid.DataSource != null &&
			    dataGrid.DataSource.AllowNew &&
			    cellContext.Position.Row == (dataGrid.Rows.Count - 1))
				return "*";
			else
				return null;
		}

		public void SetValue(CellContext cellContext, object p_Value)
		{
			throw new ApplicationException("Not supported");
		}
		#endregion
	}
	#endregion

	#region Controller
	public class DataGridCellController : Cells.Controllers.ControllerBase
	{
		public override void OnValueChanging(CellContext sender, ValueChangeEventArgs e)
		{
			base.OnValueChanging (sender, e);
            
			//BeginEdit on the row, set the Cancel = true if failed to start editing.
			bool success = ((DataGrid)sender.Grid).BeginEditRow(sender.Position.Row);
			if (success == false)
				throw new SourceGridException("Failed to editing row " + sender.Position.Row.ToString());
		}

		public override void OnEditStarting(CellContext sender, CancelEventArgs e)
		{
			base.OnEditStarting (sender, e);
            //sandhra.prakash@siemens.com: if e.Cancel is already set to true in some other
            //controller no need to proceed
            if(e.Cancel)
                return;
			//BeginEdit on the row, set the Cancel = true if failed to start editing.
			bool success = ((DataGrid)sender.Grid).BeginEditRow(sender.Position.Row);
			e.Cancel = !success;
		}
	}
	#endregion


	[Serializable]
	public class EndEditingException : SourceGridException
	{
		public EndEditingException(Exception innerException):
			base(innerException.Message, innerException)
		{
		}
		protected EndEditingException(System.Runtime.Serialization.SerializationInfo p_Info, System.Runtime.Serialization.StreamingContext p_StreamingContext):
			base(p_Info, p_StreamingContext)
		{
		}
	}
}

